package com.flca.dbs.berkeley;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.sleepycat.bind.tuple.LongBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;

public abstract class DaoHelperBerkeleyBase {

	protected abstract Environment getEnvironment();
	protected abstract Map<String, Database> getDatabases();
	protected abstract String getDatabaseDir();
	protected abstract Transaction getTransaction();
	protected abstract void setData(Environment aEnv, Map<String, Database> aDbsMap);
	
	private static final String SEQUENCE_TABLE = "_SEQUENCE";

	private static long sSeqNr = new Date().getTime();
	
	protected void setup()
	{
		if (getEnvironment() == null) {
			setupEnvironment();
		}
	}
	
	
	public long doSave(Object aSource, IBerkeleyHelper aIntfHelper)
	{
		long pk = getPk(aIntfHelper.getPk(aSource));
		
		try {
			DatabaseEntry keyEntry = new DatabaseEntry();
            LongBinding.longToEntry(pk, keyEntry);
            
            Database dbs = getDatabase(aIntfHelper.getDatabaseName());
            DatabaseEntry dataEntry = new DatabaseEntry();
            aIntfHelper.toEntry(aSource, dataEntry);
            OperationStatus status = dbs.put(getTransaction(), keyEntry, dataEntry);

            if (status == OperationStatus.SUCCESS) {
            	return pk;
            } else {
            	DatabaseException dbsEx = new DatabaseException("Data insertion got status " + status);
            	throw new RuntimeException(dbsEx);
            }
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
	
	public Object retrieve(Long aPk, IBerkeleyHelper aIntfHelper)
	{
		Object result = null;
		
		if (aPk == null || aPk.intValue() == 0) {
			System.out.println("retrieve called with Pk = null or 0");
			return result;
		}
		
		long pk = aPk.longValue();

		try {
			DatabaseEntry keyEntry = new DatabaseEntry();
			 LongBinding.longToEntry(pk, keyEntry);

            Database dbs = getDatabase(aIntfHelper.getDatabaseName());

            DatabaseEntry dataEntry = new DatabaseEntry();
            OperationStatus status = dbs.get(getTransaction(), keyEntry, dataEntry, LockMode.DEFAULT);

            if (status == OperationStatus.SUCCESS) {
            	result = aIntfHelper.toObject(dataEntry);
            } else {
            	DatabaseException dbsEx = new DatabaseException("Data retrieve got status " + status);
            	throw new RuntimeException(dbsEx);
            }
        	return result;
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	public List<?> findAll(IBerkeleyHelper aIntfHelper)
	{
		List<Object> result = new ArrayList<Object>();

		Cursor cursor = null;
    	try {
			DatabaseEntry dbKeyEntry = new DatabaseEntry();
			DatabaseEntry dbDataEntry = new DatabaseEntry();
			
            Database dbs = getDatabase(aIntfHelper.getDatabaseName());

			cursor = dbs.openCursor(null, null);
			OperationStatus status = cursor.getFirst(dbKeyEntry, dbDataEntry, LockMode.DEFAULT);
			if (status == OperationStatus.SUCCESS && cursor.count() > 0) {
				while (status == OperationStatus.SUCCESS) {

					Object obj = aIntfHelper.toObject(dbDataEntry);
					result.add(obj);
            		
				}
			}
			return result;
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			try {
				cursor.close();
			} catch (DatabaseException e) {
				e.printStackTrace();
			}
		}
	}

	private long getPk(Long aPk) 
	{
		if (aPk != null && aPk.intValue() > 0) {
			return aPk.intValue();
		} else {
			return sSeqNr++; //TODO vervang mbv sequence tabel
		}
	}
	
	private Database getDatabase(String aDatabaseName)
	{
		return getDatabases().get(aDatabaseName);
	}
	
	private synchronized void setupEnvironment()
	{
		Environment dbEnv = null;
		try {
			EnvironmentConfig envConfig = new EnvironmentConfig();
			envConfig.setTransactional(true);
			envConfig.setAllowCreate(true);
			
			File envdir = new File(getDatabaseDir());
			System.out.println("Open Berkeley postcode tables using envdir = " + envdir);
			dbEnv = new Environment(envdir, envConfig);

			DatabaseConfig dbConfig = new DatabaseConfig();
			dbConfig.setTransactional(true);
			dbConfig.setAllowCreate(true);
			dbConfig.setSortedDuplicates(true);
			Transaction txn = dbEnv.beginTransaction(null, null); 

			Map<String, Database> dbsMap = new HashMap<String, Database>();
			Set<String> dbsList = getDatabases().keySet();
			for (String dbsname : dbsList) {
				Database db = dbEnv.openDatabase(null, dbsname, dbConfig);
				dbsMap.put(dbsname, db);
			}
			
			//add final dbs that will maintain the sequence ids
			Database db = dbEnv.openDatabase(null, SEQUENCE_TABLE, dbConfig);
			dbsMap.put(SEQUENCE_TABLE, db);
			
			txn.commit();
			
			setData(dbEnv, dbsMap);
			
		} catch (DatabaseException e) {
			throw new RuntimeException(e);
		}
	}

}
